/*
 *
 *  *****************************************************************************
 *  * Copyright ( c ) 2016 Heren Tianjin Inc. All Rights Reserved.
 *  *
 *  * This software is the confidential and proprietary information of Heren Tianjin Inc
 *  * ("Confidential Information").  You shall not disclose such Confidential Information
 *  *  and shall use it only in accordance with the terms of the license agreement
 *  *  you entered into with Heren Tianjin or a Heren Tianjin authorized
 *  *  reseller (the "License Agreement").
 *  ****************************************************************************
 *  *
 */

package com.herench.bs.support;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;

public final class ServiceHelper {

    protected static final Logger logger = LoggerFactory.getLogger(ServiceHelper.class);

    /**
     * Utility classes should not have a public constructor.
     */
    private ServiceHelper() {
    }

    /**
     * Starts the given {@code value} if it's a {@link Service} or a collection of it.
     * <p/>
     * Calling this method has no effect if {@code value} is {@code null}.
     *
     * @see #startService(Service)
     * @see #startServices(Collection)
     */
    public static void startService(Object value) throws Exception {
        if (value instanceof Service) {
            startService((Service) value);
        } else if (value instanceof Collection) {
            startServices((Collection<?>) value);
        }
    }

    /**
     * Starts the given {@code service}.
     * <p/>
     * Calling this method has no effect if {@code service} is {@code null}.
     *
     * @see Service#start()
     */
    public static void startService(Service service) throws Exception {
        if (service != null) {
            service.start();
        }
    }

    /**
     * Starts each element of the given {@code services} if {@code services} itself is
     * not {@code null}, otherwise this method would return immediately.
     *
     * @see #startServices(Collection)
     */
    public static void startServices(Object... services) throws Exception {
        if (services == null) {
            return;
        }
        List<Object> list = Arrays.asList(services);
        startServices(list);
    }

    /**
     * Starts each element of the given {@code services} if {@code services} itself is
     * not {@code null}, otherwise this method would return immediately.
     */
    public static void startServices(Collection<?> services) throws Exception {
        if (services == null) {
            return;
        }
        for (Object value : services) {
            startService(value);
        }
    }

    /**
     * Stops each element of the given {@code services} if {@code services} itself is
     * not {@code null}, otherwise this method would return immediately.
     * <p/>
     * If there's any exception being thrown while stopping the elements one after the
     * other this method would rethrow the <b>first</b> such exception being thrown.
     *
     * @see #stopServices(Collection)
     */
    public static void stopServices(Object... services) throws Exception {
        if (services == null) {
            return;
        }
        List<Object> list = Arrays.asList(services);
        stopServices(list);
    }

    /**
     * Stops the given {@code value}, rethrowing the first exception caught.
     * <p/>
     * Calling this method has no effect if {@code value} is {@code null}.
     *
     * @see Service#stop()
     * @see #stopServices(Collection)
     */
    public static void stopService(Object value) throws Exception {
        if (isStopped(value)) {
            // only stop service if not already stopped
            logger.trace("Service already stopped: {}", value);
            return;
        }
        if (value instanceof Service) {
            Service service = (Service) value;
            logger.trace("Stopping service {}", value);
            service.stop();
        } else if (value instanceof Collection) {
            stopServices((Collection<?>) value);
        }
    }

    /**
     * Stops each element of the given {@code services} if {@code services} itself is
     * not {@code null}, otherwise this method would return immediately.
     * <p/>
     * If there's any exception being thrown while stopping the elements one after the
     * other this method would rethrow the <b>first</b> such exception being thrown.
     *
     * @see #stopService(Object)
     */
    public static void stopServices(Collection<?> services) throws Exception {
        if (services == null) {
            return;
        }
        Exception firstException = null;
        for (Object value : services) {
            try {
                stopService(value);
            } catch (Exception e) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Caught exception stopping service: " + value, e);
                }
                if (firstException == null) {
                    firstException = e;
                }
            }
        }
        if (firstException != null) {
            throw firstException;
        }
    }

    /**
     * Stops and shutdowns each element of the given {@code services} if {@code services} itself is
     * not {@code null}, otherwise this method would return immediately.
     * <p/>
     * If there's any exception being thrown while stopping/shutting down the elements one after
     * the other this method would rethrow the <b>first</b> such exception being thrown.
     *
     * @see #stopAndShutdownServices(Collection)
     */
    public static void stopAndShutdownServices(Object... services) throws Exception {
        if (services == null) {
            return;
        }
        List<Object> list = Arrays.asList(services);
        stopAndShutdownServices(list);
    }

    /**
     * Stops and shutdowns the given {@code service}, rethrowing the first exception caught.
     * <p/>
     * Calling this method has no effect if {@code value} is {@code null}.
     *
     * @see #stopService(Object)
     * @see ShutdownableService#shutdown()
     */
    public static void stopAndShutdownService(Object value) throws Exception {
        stopService(value);

        // then try to shutdown
        if (value instanceof ShutdownableService) {
            ShutdownableService service = (ShutdownableService) value;
            logger.trace("Shutting down service {}", value);
            service.shutdown();
        }
    }

    /**
     * Stops and shutdowns each element of the given {@code services} if {@code services}
     * itself is not {@code null}, otherwise this method would return immediately.
     * <p/>
     * If there's any exception being thrown while stopping/shutting down the elements one after
     * the other this method would rethrow the <b>first</b> such exception being thrown.
     *
     * @see #stopService(Object)
     * @see ShutdownableService#shutdown()
     */
    public static void stopAndShutdownServices(Collection<?> services) throws Exception {
        if (services == null) {
            return;
        }
        Exception firstException = null;

        for (Object value : services) {

            try {
                // must stop it first
                stopService(value);

                // then try to shutdown
                if (value instanceof ShutdownableService) {
                    ShutdownableService service = (ShutdownableService) value;
                    logger.trace("Shutting down service: {}", service);
                    service.shutdown();
                }
            } catch (Exception e) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Caught exception shutting down service: " + value, e);
                }
                if (firstException == null) {
                    firstException = e;
                }
            }
        }
        if (firstException != null) {
            throw firstException;
        }
    }

    /**
     * Resumes each element of the given {@code services} if {@code services} itself is
     * not {@code null}, otherwise this method would return immediately.
     * <p/>
     * If there's any exception being thrown while resuming the elements one after the
     * other this method would rethrow the <b>first</b> such exception being thrown.
     *
     * @see #resumeService(Object)
     */
    public static void resumeServices(Collection<?> services) throws Exception {
        if (services == null) {
            return;
        }
        Exception firstException = null;
        for (Object value : services) {
            if (value instanceof Service) {
                Service service = (Service) value;
                try {
                    resumeService(service);
                } catch (Exception e) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Caught exception resuming service: " + service, e);
                    }
                    if (firstException == null) {
                        firstException = e;
                    }
                }
            }
        }
        if (firstException != null) {
            throw firstException;
        }
    }

    /**
     * Resumes the given {@code service}.
     * <p/>
     * If {@code service} is a {@link SuspendableService} then
     * it's {@link SuspendableService#resume()} is called but
     * <b>only</b> if {@code service} is already {@link #isSuspended(Object)
     * suspended}.
     * <p/>
     * If {@code service} is <b>not</b> a
     * {@link SuspendableService} then it's
     * {@link Service#start()} is called.
     * <p/>
     * Calling this method has no effect if {@code service} is {@code null}.
     *
     * @param service the service
     * @return <tt>true</tt> if either <tt>resume</tt> method or
     * {@link #startService(Service)} was called, <tt>false</tt>
     * otherwise.
     * @throws Exception is thrown if error occurred
     * @see #startService(Service)
     */
    public static boolean resumeService(Object service) throws Exception {
        if (service instanceof SuspendableService) {
            SuspendableService ss = (SuspendableService) service;
            if (ss.isSuspended()) {
                logger.debug("Resuming service {}", service);
                ss.resume();
                return true;
            } else {
                return false;
            }
        } else {
            startService(service);
            return true;
        }
    }

    /**
     * Suspends each element of the given {@code services} if {@code services} itself is
     * not {@code null}, otherwise this method would return immediately.
     * <p/>
     * If there's any exception being thrown while suspending the elements one after the
     * other this method would rethrow the <b>first</b> such exception being thrown.
     *
     * @see #suspendService(Object)
     */
    public static void suspendServices(Collection<?> services) throws Exception {
        if (services == null) {
            return;
        }
        Exception firstException = null;
        for (Object value : services) {
            if (value instanceof Service) {
                Service service = (Service) value;
                try {
                    suspendService(service);
                } catch (Exception e) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Caught exception suspending service: " + service, e);
                    }
                    if (firstException == null) {
                        firstException = e;
                    }
                }
            }
        }
        if (firstException != null) {
            throw firstException;
        }
    }

    /**
     * Suspends the given {@code service}.
     * <p/>
     * If {@code service} is a {@link SuspendableService} then
     * it's {@link SuspendableService#suspend()} is called but
     * <b>only</b> if {@code service} is <b>not</b> already
     * {@link #isSuspended(Object) suspended}.
     * <p/>
     * If {@code service} is <b>not</b> a
     * {@link SuspendableService} then it's
     * {@link Service#stop()} is called.
     * <p/>
     * Calling this method has no effect if {@code service} is {@code null}.
     *
     * @param service the service
     * @return <tt>true</tt> if either the <tt>suspend</tt> method or
     * {@link #stopService(Object)} was called, <tt>false</tt>
     * otherwise.
     * @throws Exception is thrown if error occurred
     * @see #stopService(Object)
     */
    public static boolean suspendService(Object service) throws Exception {
        if (service instanceof SuspendableService) {
            SuspendableService ss = (SuspendableService) service;
            if (!ss.isSuspended()) {
                logger.trace("Suspending service {}", service);
                ss.suspend();
                return true;
            } else {
                return false;
            }
        } else {
            stopService(service);
            return true;
        }
    }

    /**
     * Is the given service stopping or already stopped?
     *
     * @return <tt>true</tt> if stopping or already stopped, <tt>false</tt> otherwise
     * @see StatefulService#isStopping()
     * @see StatefulService#isStopped()
     */
    public static boolean isStopped(Object value) {
        if (value instanceof StatefulService) {
            StatefulService service = (StatefulService) value;
            if (service.isStopping() || service.isStopped()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Is the given service starting or already started?
     *
     * @return <tt>true</tt> if starting or already started, <tt>false</tt> otherwise
     * @see StatefulService#isStarting()
     * @see StatefulService#isStarted()
     */
    public static boolean isStarted(Object value) {
        if (value instanceof StatefulService) {
            StatefulService service = (StatefulService) value;
            if (service.isStarting() || service.isStarted()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Is the given service suspending or already suspended?
     *
     * @return <tt>true</tt> if suspending or already suspended, <tt>false</tt> otherwise
     * @see StatefulService#isSuspending()
     * @see StatefulService#isSuspended()
     */
    public static boolean isSuspended(Object value) {
        if (value instanceof StatefulService) {
            StatefulService service = (StatefulService) value;
            if (service.isSuspending() || service.isSuspended()) {
                return true;
            }
        }
        return false;
    }

}